﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text.RegularExpressions;
using MLSharp.Utilities;

namespace MLSharp.Evaluation
{
	/// <summary>
	/// Helper that uses reflection to load <see cref="IResultWriter"/> instances
	/// based on user-specified settings.
	/// </summary>
	public static class ResultWriterLoader
	{
		/// <summary>
		/// This regular expression is used to parse the result writer arguments.
		/// The raw regular expression is below.
		/// </summary>
		//(?:\s+(?<Name>\w+):((?<Value>[\w\.]+)|\{(?<Value>[a-zA-Z0-9\-\\\ \:\._]+)\}))
		public static Regex mParameterParser = new Regex(
		  "(?:\\s+(?<Name>\\w+):((?<Value>[\\w\\.]+)|\\{(?<Value>[a-zA-" +
		  "Z0-9\\-\\\\\\ \\:\\._]+)\\}))", 
			RegexOptions.CultureInvariant
			| RegexOptions.Compiled
			);

		/// <summary>
		/// A map from a normalized type name to the actual result writer
		/// type.
		/// </summary>
		private static IDictionary<string, Type> mResultWriterTypeMap;

		/// <summary>
		/// Builds a cache of all the <see cref="IResultWriter"/>
		/// types that are available.
		/// </summary>
		static ResultWriterLoader()
		{
			//TODO: Make this look in all referenced assemblies, not just
			//the current assembly.
			mResultWriterTypeMap = new Dictionary<string, Type>();

			foreach (Type t in Assembly.GetExecutingAssembly().GetExportedTypes())
			{
				if (t.GetInterface(typeof(IResultWriter).Name) != null &&
					!t.IsAbstract &&
					!t.IsInterface)
				{
					string name = GetNormalizedName(t.Name);
					mResultWriterTypeMap.Add(name, t);
				}
			}
		}

		/// <summary>
		/// Normalizes the name to lower-case and appends "resultwriter" if it doesn't
		/// already end in "resultwriter".
		/// </summary>
		/// <param name="name"></param>
		/// <returns></returns>
		private static string GetNormalizedName(string name)
		{
			name = name.ToLower();

			if (!name.EndsWith("resultwriter"))
			{
				name += "resultwriter";
			}

			return name;
		}

		/// <summary>
		/// Loads the specified result writer.
		/// </summary>
		/// <param name="writerSpec"></param>
		/// <returns></returns>
		/// <exception cref="ResultWriterNotFoundException">Thrown if a filter isn't found.</exception>
		public static IResultWriter Load(string writerSpec)
		{
			//Parse the arguments
			int nameIndex = writerSpec.IndexOf(' ');

			if (nameIndex < 0)
			{
				nameIndex = writerSpec.Length;
			}

			string name = writerSpec.Substring(0, nameIndex);
			writerSpec = writerSpec.Remove(0, name.Length);

			string normalizedName = GetNormalizedName(name);

			if (!mResultWriterTypeMap.ContainsKey(normalizedName))
			{
				throw new ResultWriterNotFoundException(name);
			}

			//Create the writer
			IResultWriter writer = Reflector.CreateInstance<IResultWriter>(mResultWriterTypeMap[normalizedName]);

			//Parse out all the argument-value pairs
			MatchCollection matches = mParameterParser.Matches(writerSpec);

			foreach (Match match in matches)
			{
				string propertyName = match.Groups["Name"].Value;

				string propertyValue = match.Groups["Value"].Value;

				//Special case: 'path' is a shortcut for OutputFileName
				if (propertyName == "path")
				{
					propertyName = "OutputFileName";
				}

				Reflector.SetPropertyValue(writer, propertyName, propertyValue);

			}

			return writer;
		}

		/// <summary>
		/// Loads the specified result writers. 
		/// </summary>
		/// <param name="writerSpecs"></param>
		/// <returns></returns>
		/// <exception cref="ResultWriterNotFoundException">Thrown if a filter isn't found.</exception>
		public static IResultWriter[] LoadAll(string[] writerSpecs)
		{
			IResultWriter[] writers = new IResultWriter[writerSpecs.Length];

			for (int i = 0; i < writers.Length; i++)
			{
				writers[i] = Load(writerSpecs[i]);
			}

			return writers;
		}
	}
}
